﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using SimpleExpression;
namespace SQLExpressionBuilder.Commands
{
	public class ExpressionSelectCommandXmlStrategy : IExpressionSelectCommandStrategy
	{
		private XmlDocument root;
		private EntityExpression expression = new EntityExpression();
		public ExpressionSelectCommandXmlStrategy(XmlDocument document)
		{
			this.root = document;
		}

		public EntityExpression processXml()
		{
			if (this.root == null)
				throw new NotSupportedException("The xml can't be null");

			XmlNodeList nodes = this.root.SelectNodes("/fetch");
			if( nodes.Count != 1 )
				throw new NotSupportedException("The 'fetch' node must appear 1 time!, " + nodes.Count);

			return this.AddEntity(nodes[0]);
		}

		public EntityExpression AddEntity(System.Xml.XmlNode node)
		{
			XmlNodeList list = node.SelectNodes("entity");
			if(list.Count != 1)
				throw new NotSupportedException("The 'entity' node must appear 1 time!, " + list.Count);
			
			//Create expression and entity
			XmlNode entity = list[0];
						
			expression = new EntityExpression(entity.Attributes["name"].Value);
			EntityMetadata entityMetadata = expression.EntityMetadata;
			expression.EntityMetadata.Alias = entity.Attributes["alias"].Value;
			//iterate over entity node
						
			//first get the links entities
			foreach(XmlNode xmlNode in entity.SelectNodes("linkentity"))
			{
				LinkEntityExpression linkExpression = this.GetLinkEntityExpression(xmlNode, expression);
				LinkEntityExpression newLinkExpression = expression.JoinEntityExpression.Add(linkExpression.JoinOperator, linkExpression.FieldFrom, linkExpression.FieldTo);

				newLinkExpression.FilterExpression = linkExpression.FilterExpression;
			}
			
			foreach(XmlNode xmlNode in entity.ChildNodes)
			{
				switch(xmlNode.Name)
				{
					case "attribute":
						expression.ColumnSetExpression.Attributes.Add(new AttributeExpression(  GetProperty(xmlNode.Attributes["alias"].Value,xmlNode.Attributes["name"].Value,expression)));
					break;
					
					case "filter":
						FilterExpression filterExpression = this.GetFilterExpression(xmlNode, expression);
						expression.FilterExpression = filterExpression;
						break;
					case "groupby":
						expression.GroupByExpression = GetGroupByExpression(xmlNode,expression);
					break;
						
					case "orderby":
						expression.OrderExpression = GetOrderByExpression(xmlNode,expression);
					break;
					default:
					break;
				}
			}

			return expression;
		}


		public void AddLinkEntities(XmlNode node, EntityExpression expression)
		{

			foreach(XmlNode xmlNode in node.ChildNodes)
			{
				switch (xmlNode.Name)
				{
					case "linkentity":
						expression.JoinEntityExpression.LinkedEntities.Add(this.GetLinkEntityExpression(xmlNode,expression));
					break;
					default:
					break;
				}
			}
		}

		public LinkEntityExpression GetLinkEntityExpression(XmlNode node, EntityExpression parent)
		{
			string alias	= node.Attributes["alias"].Value;
			string entity = node.Attributes["entity"].Value;

			EntityMetadata entityMetadata = EntityMetadata.GetEntity(entity);
			entityMetadata.Alias = alias;
			
			//Get Filters-
			LinkEntityExpression linkEntity = new LinkEntityExpression(GetJoinOperator(node.Attributes["joinoperator"].Value)
													,parent.EntityMetadata
													,entityMetadata);
			
			foreach(XmlNode xmlNode in node.ChildNodes)
			{
				switch (xmlNode.Name)
				{ 
					case "filter":
						FilterExpression filterExp = GetFilterExpression(xmlNode, parent,linkEntity);
						linkEntity.FilterExpression = filterExp;			
					break;
				}
			}
			
			return linkEntity;
		}


		public FilterExpression GetFilterExpression(XmlNode filterXml, EntityExpression parent, LinkEntityExpression linkExpression = null)
		{
			FilterExpression filter = new FilterExpression( GetLogicOperator(filterXml.Attributes["logical-operator"].Value) );

			foreach(XmlNode node in filterXml.ChildNodes)
			{
				switch (node.Name)
				{
					case "condition":
						ConditionExpression conditionExp = GetConditionExpresssion(node, parent, filter, linkExpression);
						filter.Conditions.Add(conditionExp);
					break;
					case "filter":
						FilterExpression filterExpresion = GetFilterExpression(node, parent, linkExpression);
						filter.Conditions.Add(filterExpresion);
					break;
				}
			}

			return filter;
		}

		public ConditionExpression GetConditionExpresssion(XmlNode node, EntityExpression parent, FilterExpression expression, LinkEntityExpression linkExpression = null)
		{

			object value1 = null;
			object value2 = null;
			
			if (node.Attributes["attribute-alias"] != null)
			{
				value1 = GetProperty(node.Attributes["attribute-alias"].Value, node.Attributes["attribute"].Value, parent);
				if (value1 == null)
				{
					if (linkExpression != null)
					{
						if (linkExpression.EntityTo.Alias == node.Attributes["attribute-alias"].Value)
						{
							value1 = linkExpression.EntityTo[node.Attributes["attribute"].Value];
						}
					}
					else
					{
						throw new NotSupportedException(string.Format("The Field {0}.{1} does not exists!", node.Attributes["attribute-alias"].Value, node.Attributes["attribute"].Value));
					}
				}
			}
			else
				value1 = node.Attributes["attribute"].Value;

			if (node.Attributes["value-alias"] != null)
			{
				value2 = GetProperty(node.Attributes["value-alias"].Value, node.Attributes["value"].Value, parent);
				if (value2 == null)
				{
					if (linkExpression != null)
					{
						if (linkExpression.EntityTo.Alias == node.Attributes["value-alias"].Value)
						{
							value2 = linkExpression.EntityTo[node.Attributes["value"].Value];
						}
					}
					else
					{
						throw new NotSupportedException(string.Format("The Field {0}.{1}  does not exists!", node.Attributes["value-alias"].Value, node.Attributes["value"].Value));
					}
				}
			}
			else
				value2 = node.Attributes["value"].Value;
						
			ConditionExpression condition = new ConditionExpression(GetLogicOperator(node.Attributes["logical-operator"].Value),value1, GetConditionOperator(node.Attributes["condition-operator"].Value), value2 );
			foreach(XmlNode xmlNode in node.ChildNodes )
			{
				switch(xmlNode.Name)
				{
					default:
						break;
				}
			}
			return condition;
		}


		public GroupByExpression GetGroupByExpression(XmlNode groupByNode, EntityExpression expression)
		{
			GroupByExpression groupbyExpression = new GroupByExpression();
			
			foreach(XmlNode node in groupByNode.ChildNodes)
			{
				switch(node.Name)
				{
					case "attribute":
						if (node.Attributes["alias"] != null)
						{
							groupbyExpression.Add(GetProperty(node.Attributes["alias"].Value, node.Attributes["name"].Value, expression));
						}
						else
						{
							groupbyExpression.Add(node.Attributes["name"].Value);
						}
					break;
				}
			}
			return groupbyExpression;
		}

		public OrderExpression GetOrderByExpression(XmlNode orderNode, EntityExpression expression)
		{

			OrderExpression orderExpression = new OrderExpression();
			
			foreach(XmlNode node in orderNode.ChildNodes)
			{
				switch(node.Name)
				{
					case "order":
						if (node.Attributes["alias"] != null)
						{
							orderExpression.Add(GetProperty(node.Attributes["alias"].Value, node.Attributes["name"].Value, expression), GetOrderType(node.Attributes["type"].Value));
						}
						else
							throw new NotImplementedException("The order by must be only properties");
							/*
							node.Attributes["name"].Value, GetOrderType(node.Attributes["type"].Value)
							orderExpression.Add(null, OrderType.ASC);
							 * */
					break;
				}
			}
			return orderExpression;
		}


		public EntityExpression GetExpression()
		{
			return this.expression;
		}
		public static ConditionOperator GetConditionOperator(string value)
		{
			switch(value)
			{
				case "=":
					return ConditionOperator.Equals;
				case "ne":
					return ConditionOperator.Different;
				case "in":
					return ConditionOperator.In;
 				case "gt":
					return ConditionOperator.GreaterThan;
				case "lt":
					return ConditionOperator.LessThan;
				case "gte":
					return ConditionOperator.GreaterThanOrEqual;
				case "lte":
					return ConditionOperator.LessThanOrEqual;
			}
			return ConditionOperator.Equals;
		}

		public static LogicalOperator GetLogicOperator(string value)
		{
			switch(value.ToUpper())
			{
				case "AND":
					return LogicalOperator.And;
				case "OR":
					return LogicalOperator.Or;
				default :
					return LogicalOperator.None;
			}
		}

		public static JoinOperator GetJoinOperator(string operatorName)
		{
			switch (operatorName.ToUpper())
			{
				case "INNER":
					return JoinOperator.INNER;
				case "LEFT":
					return JoinOperator.LEFT;
				case "CROSS":
					return JoinOperator.CROSS;
				case "RIGHT":
					return JoinOperator.RIGHT;
				case "LEFTOUTER":
					return JoinOperator.LEFTOUTER;
				case "RIGHTOUTER":
					return JoinOperator.RIGHTOUTER;
				default:
					return JoinOperator.INNER;
			}
		}

		public static OrderType GetOrderType(string type)
		{
			switch(type.ToUpper())
			{
				case "ASC":
					return OrderType.ASC;
				case "DESC":
					return OrderType.DESC;
				default :
					return OrderType.ASC;
			}
		}

		public static PropertyMetadata GetProperty(string alias, string fieldPhysicalName, EntityExpression entityExperssion)
		{
			if (alias == entityExperssion.EntityMetadata.Alias)
				return entityExperssion.EntityMetadata[fieldPhysicalName];
			else
			{
				foreach(LinkEntityExpression link in entityExperssion.JoinEntityExpression.LinkedEntities )
				{
					if (link.EntityTo.Alias == alias)
						return link.EntityTo[fieldPhysicalName];
					if (link.EntityFrom.Alias == alias)
						return link.EntityFrom[fieldPhysicalName];
				}

			}
			return null;
		}
	}
}
